/*
 * Copyright (c) Hisilicon Technologies Co., Ltd. 2019-2019. All rights reserved.
 * Description: timestamp log functions.
 * Author: IP Camera Reference Develop Team
 * Create: 2019-06-28
 */

#include "hi_timestamp.h"

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include "hi_appcomm_util.h"
#include "hi_osal.h"
#include "chip_config.h"

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif /* __cplusplus */

/*
 * timestamps storage arrangement
 * |---------16 bytes------|-----Timestamp Item----|-----Timestamp Item----| ...
 * |Count|     |     |     |Stamp|char*|Line |Type |Stamp|char*|Line |Type | ...
 */
#if (defined(TIMER_REG_BASE) && defined(SC_CTRL))
/* defined in SoC Data Sheet */
#define TIMER_REG_VALUE   (TIMER_REG_BASE + 0x004)
#define TIMER_REG_CONTROL (TIMER_REG_BASE + 0x008)
#ifndef TIMER_CLK_MASK
#define TIMER_CLK_MASK (1 << 18) /* timeren2ov */
#endif
#define BUS_CLK   50000000U /* sysapb_cksel */
#define TIMER_CLK 3000000U  /* timer default cksel */
#define NS2MS     1000000U

#define TIMESTAMP_MAX_CNT 200U
#define TIMESTAMP_VALUE_ADDR_OFFSET 0x10 /* 0x10: 16bytes offset */

#define GET_MS_CLOCK(value) ((HI_U32)((HI_U64)value * g_timestampCtx.timerpre / (g_timestampCtx.timerClock / 1000U)))

/* TimeStamp Context */
typedef struct {
    HI_UINTPTR_T phyMemAddr;
    HI_U32 timerClock;
    HI_U32 timerpre;
    pthread_mutex_t mutex;
    HI_TIMESTAMP_Item *items;
} TIMESTAMP_Context;

static TIMESTAMP_Context g_timestampCtx = {
    .phyMemAddr = 0,
    .timerClock = BUS_CLK,
    .timerpre = 256U,
    .mutex = PTHREAD_MUTEX_INITIALIZER,
    .items = HI_NULL,
};

HI_S32 TIMESTAMP_GetTimerAttr(HI_VOID)
{
    /* get timer control */
    HI_U32 timerctrl;
    HI_ReadReg(TIMER_REG_CONTROL, &timerctrl);
    if ((timerctrl & (1 << 7)) == 0) { /* bit[7] of timerctrl is timer enable */
        MLOGE("Timer is not enable.\n");
        return HI_ENOTREADY;
    }

    /* get timer clock */
    HI_U32 timerenov;
    HI_ReadReg(SC_CTRL, &timerenov);
    timerenov = timerenov & TIMER_CLK_MASK;
    g_timestampCtx.timerClock = (timerenov == 0) ? TIMER_CLK : BUS_CLK;

    /* get timer prescaling factor configuration */
    switch (timerctrl & 0xc) { /* 0xc: timerpre is bits[3:2] of timerctrl */
        case 0x0: /* 0x0: no prescaling */
            g_timestampCtx.timerpre = 1U; /* the clock frequency of the timer is divided by 1. */
            break;
        case 0x4: /* 0x4: 4-level prescaling */
            g_timestampCtx.timerpre = 16U;  /* the clock frequency of the timer is divided by 16. */
            break;
        case 0x8: /* 0x8: 8-level prescaling */
        case 0xc: /* 0xc: undefined. If the bits are set to 11, 8-level prescaling is considered */
            g_timestampCtx.timerpre = 256U; /* the clock frequency of the timer is divided by 256 */
            break;
    }
    return HI_SUCCESS;
}

HI_S32 HI_TIMESTAMP_Init(HI_UINTPTR_T phyMemAddr, HI_BOOL cleanMem)
{
    HI_APPCOMM_RETURN_IF_PTR_NULL(phyMemAddr, HI_EINVAL);

    HI_MUTEX_LOCK(g_timestampCtx.mutex);
    if (g_timestampCtx.phyMemAddr != 0) {
        if (g_timestampCtx.phyMemAddr == phyMemAddr) {
            HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
            return HI_SUCCESS;
        } else {
            HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
            return HI_EINITIALIZED;
        }
    }
    /* get timer clock */
    if (TIMESTAMP_GetTimerAttr() != HI_SUCCESS) {
        HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
        return HI_ENOTREADY;
    }

#ifndef __HuaweiLite__
    g_timestampCtx.items = (HI_TIMESTAMP_Item *)HI_MemMap((phyMemAddr + TIMESTAMP_VALUE_ADDR_OFFSET),
        TIMESTAMP_MAX_CNT * sizeof(HI_TIMESTAMP_Item));
    if (g_timestampCtx.items == HI_NULL) {
        HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
        MLOGE("HI_MemMap fail.\n");
        return HI_FAILURE;
    }
#else
    g_timestampCtx.items = (HI_TIMESTAMP_Item *)(phyMemAddr + TIMESTAMP_VALUE_ADDR_OFFSET);
#endif
    if (cleanMem == HI_TRUE) {
        HI_WriteReg(phyMemAddr, 0);
    }
    g_timestampCtx.phyMemAddr = phyMemAddr;
    HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
    return HI_SUCCESS;
}

HI_VOID HI_TIMESTAMP_Deinit(HI_VOID)
{
    HI_MUTEX_LOCK(g_timestampCtx.mutex);
    if (g_timestampCtx.phyMemAddr == 0) {
        HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
        return;
    }
    if (g_timestampCtx.items != HI_NULL) {
#ifndef __HuaweiLite__
        HI_MemUnmap(g_timestampCtx.items);
#endif
        g_timestampCtx.items = HI_NULL;
    }
    g_timestampCtx.phyMemAddr = 0;
    HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
}

HI_S32 HI_TIMESTAMP_Mark(const HI_CHAR *func, HI_U32 line, HI_U32 type)
{
    HI_MUTEX_LOCK(g_timestampCtx.mutex);

    if (g_timestampCtx.phyMemAddr == 0) {
        HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
        return HI_ENOINIT;
    }

    HI_U32 cnt;
    HI_ReadReg(g_timestampCtx.phyMemAddr, &cnt);
    if (cnt > TIMESTAMP_MAX_CNT) {
        HI_WriteReg(g_timestampCtx.phyMemAddr, 0);
        cnt = 0;
        MLOGW("\n time_stamp is full!!! now clean the buffer to start from 0\n");
    }

    HI_TIMESTAMP_Item *item = g_timestampCtx.items;
#ifdef USE_ARM_TIMER
    item[cnt].stamp = (HI_U32)(osal_sched_clock() / NS2MS);
#else
    HI_U32 stamp;
    HI_ReadReg((HI_UL)TIMER_REG_VALUE, &stamp);
    item[cnt].stamp = ~stamp;
#endif
    item[cnt].func = (HI_CHAR *)func;
    item[cnt].line = line;
    item[cnt].type = type;

    cnt++;
    HI_WriteReg(g_timestampCtx.phyMemAddr, cnt);

    HI_MUTEX_UNLOCK(g_timestampCtx.mutex);

    return HI_SUCCESS;
}

HI_S32 HI_TIMESTAMP_MarkEx(const HI_TIMESTAMP_Item *item)
{
    if (item == HI_NULL) {
        return HI_FAILURE;
    }
    HI_U32 curStamp;
    HI_ReadReg((HI_UL)TIMER_REG_VALUE, &curStamp);
    if (item->stamp > curStamp) {
        MLOGE("Invalid timestamp value,stamp(%u) > curStamp(%u)\n", item->stamp, curStamp);
        return HI_FAILURE;
    }

    HI_MUTEX_LOCK(g_timestampCtx.mutex);
    if (g_timestampCtx.phyMemAddr == 0) {
        HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
        return HI_ENOINIT;
    }
    HI_U32 cnt;
    HI_ReadReg(g_timestampCtx.phyMemAddr, &cnt);
    if (cnt > TIMESTAMP_MAX_CNT) {
        HI_WriteReg((HI_U32)g_timestampCtx.phyMemAddr, 0);
        cnt = 0;
        MLOGW("\n time_stamp is full!!! now clean the buffer to start from 0\n");
    }

    HI_TIMESTAMP_Item *timeItem = g_timestampCtx.items;
    errno_t err = memcpy_s(&timeItem[cnt], sizeof(HI_TIMESTAMP_Item), item, sizeof(HI_TIMESTAMP_Item));
    if (err != EOK) {
        HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
        return HI_FAILURE;
    }

    cnt++;
    HI_WriteReg(g_timestampCtx.phyMemAddr, cnt);
    HI_MUTEX_UNLOCK(g_timestampCtx.mutex);

    return HI_SUCCESS;
}

HI_S32 HI_TIMESTAMP_Clean(HI_VOID)
{
    HI_MUTEX_LOCK(g_timestampCtx.mutex);

    if (g_timestampCtx.phyMemAddr == 0) {
        HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
        return HI_ENOINIT;
    }

    HI_WriteReg(g_timestampCtx.phyMemAddr, 0);

    HI_MUTEX_UNLOCK(g_timestampCtx.mutex);

    return HI_SUCCESS;
}

HI_S32 HI_TIMESTAMP_Print(HI_U32 type)
{
    HI_MUTEX_LOCK(g_timestampCtx.mutex);

    if (g_timestampCtx.phyMemAddr == 0) {
        HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
        MLOGE("TimeStamp is not init.\n");
        return HI_ENOINIT;
    }

    HI_U32 cnt;
    HI_ReadReg(g_timestampCtx.phyMemAddr, &cnt);

    HI_U32 time0 = 0;
    HI_TIMESTAMP_Item *items = g_timestampCtx.items;
    HI_U32 stamp = 0;

    for (HI_U32 i = 0; i < cnt; i++) {
#ifdef USE_ARM_TIMER
        HI_U32 nTime = items[i].stamp;
#else
        HI_U32 nTime = GET_MS_CLOCK(items[i].stamp);
#endif
        HI_CHAR *func = items[i].func;
        HI_U32 nLine = items[i].line;
        HI_U32 nType = items[i].type;

        if ((type == 0) || (type == nType)) {
            if (func != HI_NULL) {
                printf("time stamp[%-3u] = %-8ums gap = %-8ums type: %-5u  line: %-5u func: %s\n",
                       i + 1, nTime, nTime - stamp, nType, nLine, func);
            } else {
                printf("time stamp[%-3u] = %-8ums gap = %-8ums type: %-5u  line: %-5u\n",
                       i + 1, nTime, nTime - stamp, nType, nLine);
            }
        }

        stamp = nTime;

        if (i == 0) {
            time0 = nTime;
        }

        if (i == (cnt - 1)) {
            printf("total time from stamp[%-3d] to [%-3d]  gap = %-8dms\n", 1, i + 1, nTime - time0);
        }
    }

    HI_MUTEX_UNLOCK(g_timestampCtx.mutex);

    return HI_SUCCESS;
}

HI_U32 HI_TIMESTAMP_GetMs(HI_VOID)
{
    HI_U32 curTime;

    HI_ReadReg(TIMER_REG_VALUE, &curTime);

    HI_MUTEX_LOCK(g_timestampCtx.mutex);
    if (g_timestampCtx.phyMemAddr == 0) {
        HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
        return 0;
    }

    curTime = ~curTime;
    curTime = GET_MS_CLOCK(curTime);
    HI_MUTEX_UNLOCK(g_timestampCtx.mutex);
    return curTime;
}
#endif /* (defined(TIMER_REG_BASE) && defined(SC_CTRL)) */

HI_S32 HI_usleep(HI_U32 usec)
{
    HI_S32 s32Ret;
    struct timespec requst;
    struct timespec remain;
    remain.tv_sec = usec / 1000000;
    remain.tv_nsec = (usec % 1000000) * 1000;
    do
    {
        requst = remain;
        s32Ret = nanosleep(&requst, &remain);
    }while(-1 == s32Ret && errno == EINTR);
    return s32Ret;
}

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */

